
package com.tonyodev.fetch2;

import ohos.aafwk.content.Intent;
import ohos.aafwk.content.IntentParams;
import ohos.app.Context;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.event.intentagent.IntentAgent;
import ohos.event.intentagent.IntentAgentConstant;
import ohos.event.intentagent.IntentAgentHelper;
import ohos.event.intentagent.IntentAgentInfo;
import ohos.event.notification.NotificationActionButton;
import ohos.event.notification.NotificationHelper;
import ohos.event.notification.NotificationRequest;
import ohos.event.notification.NotificationSlot;
import ohos.global.resource.NotExistException;
import ohos.global.resource.Resource;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Size;
import ohos.rpc.RemoteException;
import ohos.utils.net.Uri;

import com.tonyodev.fetch2.util.Defaults;
import com.tonyodev.fetch2.util.NotificationUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The default notification manager class for Fetch. This manager supports both single
 * download notifications and grouped download notifications. Extend this class to provide your own
 * custom implementation.
 *
 * @since 2021-05-20
 */
public abstract class DefaultFetchNotificationManager implements FetchNotificationManager {

    private final ohos.app.Context context;
    private final Map<Integer, DownloadNotification> downloadNotificationsMap = new LinkedHashMap<>();
    private final Map<Integer, NotificationRequest> downloadNotificationsBuilderMap = new LinkedHashMap<>();
    private final Set<Integer> downloadNotificationExcludeSet = new LinkedHashSet<>();

    private final String notificationManagerAction = "DEFAULT_FETCH2_NOTIFICATION_MANAGER_ACTION_" + System.currentTimeMillis();
    private CommonEventSubscriber broadcastReceiver;

    /**
     * 创建通知
     *
     * @param context 上下文
     */
    public DefaultFetchNotificationManager(Context context) {
        this.context = context.getApplicationContext();
        initialize();
    }

    private void initialize() {
        registerBroadcastReceiver();
        createNotificationChannels(context);
    }

    @Override
    public String getChannelId(int notificationId, Context context) {
        return "fetch_downloads_channel_id";
    }

    @Override
    public void createNotificationChannels(Context context) {
        String channelId = "fetch_downloads_channel_id";
        String channelName = "Downloads";
        try {
            NotificationSlot slot = NotificationHelper.getNotificationSlot(channelId);
            if (slot == null) {
                slot = new NotificationSlot(channelId, channelName, NotificationSlot.LEVEL_DEFAULT);
                NotificationHelper.addNotificationSlot(slot);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean updateGroupSummaryNotification(int groupId,
                                                  NotificationRequest notificationRequest,
                                                  List<DownloadNotification> downloadNotifications,
                                                  Context context) {

        NotificationRequest.NotificationMultiLineContent content = new NotificationRequest.NotificationMultiLineContent();
        for (DownloadNotification downloadNotification : downloadNotifications) {

            String subtitleText = getSubtitleText(context, downloadNotification);
            long total = downloadNotification.getTotal();
            content.addSingleLine(total + " " + subtitleText);
        }
        content.setText("");
        content.setTitle("Downloads");
        notificationRequest.setLittleIcon(createMap(ResourceTable.Media_stat_sys_download_anim0));
        notificationRequest.setGroupValue(String.valueOf(groupId));
        return false;
    }

    private PixelMap createMap(int res) {
        try {
            Resource resource = context.getResourceManager().getResource(res);
            ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions();
            srcOpts.formatHint = "image/png";
            ImageSource imageSource = ImageSource.create(resource, srcOpts);

            ImageSource.DecodingOptions decodingOpts = new ImageSource.DecodingOptions();
            decodingOpts.desiredSize = new Size(40, 40);
            return imageSource.createPixelmap(decodingOpts);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotExistException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void updateNotification(NotificationRequest request,
                                   DownloadNotification downloadNotification,
                                   Context context) {
        PixelMap icon;
        if (downloadNotification.isDownloading()) {
            icon = createMap(ResourceTable.Media_stat_sys_download_anim5);
        } else {
            icon = createMap(ResourceTable.Media_stat_sys_download_anim0);
        }
        request.setLittleIcon(icon);
        request.setGroupValue(String.valueOf(downloadNotification.getGroupId()));
        NotificationRequest.NotificationNormalContent normalContent = new NotificationRequest.NotificationNormalContent();
        normalContent.setTitle(downloadNotification.getTitle());
        normalContent.setText(getSubtitleText(context, downloadNotification));
        NotificationRequest.NotificationContent content = new NotificationRequest.NotificationContent(normalContent);
        request.setContent(content);

        if (downloadNotification.isFailed() || downloadNotification.isCompleted()) {
            request.setProgressBar(0, 0, false);
        } else {
            boolean progressIndeterminate = downloadNotification.getProgressIndeterminate();
            int maxProgress = downloadNotification.getProgress() < 0 ? 0 : 100;
            int progress = Math.max(downloadNotification.getProgress(), 0);
            request.setProgressBar(maxProgress, progress, progressIndeterminate);
        }
        if (downloadNotification.isDownloading()) {
            request.setAutoDeletedTime(getNotificationTimeOutMillis());
            NotificationActionButton pauseAction = new NotificationActionButton.Builder(createMap(ResourceTable.Media_fetch_notification_pause),
                "Pause",
                getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.PAUSE))
                .build();

            NotificationActionButton cancelAction = new NotificationActionButton.Builder(
                createMap(ResourceTable.Media_fetch_notification_cancel),
                "Cancel",
                getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.CANCEL))
                .build();
            request.addActionButton(pauseAction);
            request.addActionButton(cancelAction);
        } else if (downloadNotification.isPaused()) {
            request.setAutoDeletedTime(getNotificationTimeOutMillis());
            NotificationActionButton resumeAction = new NotificationActionButton.Builder(
                createMap(ResourceTable.Media_fetch_notification_resume),
                "Resume",
                getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.RESUME))
                .build();

            NotificationActionButton cancelAction = new NotificationActionButton.Builder(
                createMap(ResourceTable.Media_fetch_notification_cancel),
                "Cancel",
                getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.CANCEL))
                .build();
            request.addActionButton(resumeAction);
            request.addActionButton(cancelAction);
        } else if (downloadNotification.isQueued()) {
            request.setAutoDeletedTime(getNotificationTimeOutMillis());
        } else {
            request.setAutoDeletedTime(Defaults.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET);
        }
    }

    @Override
    public IntentAgent getActionPendingIntent(DownloadNotification downloadNotifications,
                                              DownloadNotification.ActionType actionType) {
        synchronized (downloadNotificationsMap) {
            IntentParams params = new IntentParams();
            params.setParam(FetchIntent.EXTRA_NAMESPACE, downloadNotifications.getNamespace());
            params.setParam(FetchIntent.EXTRA_DOWNLOAD_ID, downloadNotifications.getNotificationId());
            params.setParam(FetchIntent.EXTRA_NOTIFICATION_ID, downloadNotifications.getNotificationId());
            params.setParam(FetchIntent.EXTRA_GROUP_ACTION, false);
            params.setParam(FetchIntent.EXTRA_NOTIFICATION_GROUP_ID, downloadNotifications.getGroupId());
            int action;
            switch (actionType) {
                case CANCEL:
                    action = FetchIntent.ACTION_TYPE_CANCEL;
                    break;
                case DELETE:
                    action = FetchIntent.ACTION_TYPE_DELETE;
                    break;
                case RESUME:
                    action = FetchIntent.ACTION_TYPE_RESUME;
                    break;
                case PAUSE:
                    action = FetchIntent.ACTION_TYPE_PAUSE;
                    break;
                case RETRY:
                    action = FetchIntent.ACTION_TYPE_RETRY;
                    break;
                default:
                    action = FetchIntent.ACTION_TYPE_INVALID;
            }
            params.setParam(FetchIntent.EXTRA_ACTION_TYPE, action);

            List<IntentAgentConstant.Flags> flags = new ArrayList<>();
            flags.add(IntentAgentConstant.Flags.UPDATE_PRESENT_FLAG);
            // 通过Intent在设置一遍，不确定和对应的是哪个
            Intent intent = new Intent();
            intent.setParam(FetchIntent.EXTRA_ACTION_TYPE, action);
            intent.setParam(FetchIntent.EXTRA_GROUP_ACTION, false);
            intent.setParam(FetchIntent.EXTRA_NAMESPACE, downloadNotifications.getNamespace());
            intent.setParam(FetchIntent.EXTRA_DOWNLOAD_ID, downloadNotifications.getNotificationId());
            intent.setParam(FetchIntent.EXTRA_NOTIFICATION_ID, downloadNotifications.getNotificationId());
            intent.setParam(FetchIntent.EXTRA_NOTIFICATION_GROUP_ID, downloadNotifications.getGroupId());
            intent.setOperation(new Intent.OperationBuilder()
                .withAction(getNotificationManagerAction())
                .build());
            List<Intent> intents = new ArrayList<>();
            intents.add(intent);

            IntentAgentInfo info = new IntentAgentInfo(downloadNotifications.getNotificationId() + action, IntentAgentConstant.OperationType.SEND_COMMON_EVENT, flags, intents, params);
            return IntentAgentHelper.getIntentAgent(context, info);
        }
    }

    @Override
    public IntentAgent getGroupActionPendingIntent(int groupId, List<DownloadNotification> downloadNotifications, DownloadNotification.ActionType actionType) {
        synchronized (downloadNotificationsMap) {
            IntentParams params = new IntentParams();
            params.setParam(FetchIntent.EXTRA_GROUP_ACTION, true);
            params.setParam(FetchIntent.EXTRA_NOTIFICATION_GROUP_ID, groupId);
            params.setParam(FetchIntent.EXTRA_DOWNLOAD_NOTIFICATIONS, new ArrayList<>(downloadNotifications));
            int action;
            switch (actionType) {
                case CANCEL:
                    action = FetchIntent.ACTION_TYPE_CANCEL;
                    break;
                case DELETE:
                    action = FetchIntent.ACTION_TYPE_DELETE;
                    break;
                case RESUME:
                    action = FetchIntent.ACTION_TYPE_RESUME;
                    break;
                case PAUSE:
                    action = FetchIntent.ACTION_TYPE_PAUSE;
                    break;
                case RETRY:
                    action = FetchIntent.ACTION_TYPE_RETRY;
                    break;
                default:
                    action = FetchIntent.ACTION_TYPE_INVALID;
            }
            params.setParam(FetchIntent.EXTRA_ACTION_TYPE, action);

            List<IntentAgentConstant.Flags> flags = new ArrayList<>();
            flags.add(IntentAgentConstant.Flags.UPDATE_PRESENT_FLAG);
            // 通过Intent在设置一遍，不确定和android对应的PendingIntent是哪个
            Intent intent = new Intent();
            intent.setParam(FetchIntent.EXTRA_GROUP_ACTION, true);
            intent.setParam(FetchIntent.EXTRA_NOTIFICATION_GROUP_ID, groupId);
            intent.setParam(FetchIntent.EXTRA_DOWNLOAD_NOTIFICATIONS, new ArrayList<>(downloadNotifications));
            intent.setOperation(new Intent.OperationBuilder()
                .withAction(getNotificationManagerAction())
                .build());
            List<Intent> intents = new ArrayList<>();
            intents.add(intent);

            IntentAgentInfo info = new IntentAgentInfo(groupId + action, IntentAgentConstant.OperationType.SEND_COMMON_EVENT, flags, intents, params);
            return IntentAgentHelper.getIntentAgent(context, info);
        }
    }

    @Override
    public void cancelNotification(int notificationId) {
        synchronized (downloadNotificationsMap) {
            try {
                NotificationHelper.cancelNotification(notificationId);
                downloadNotificationsBuilderMap.remove(notificationId);
                downloadNotificationExcludeSet.remove(notificationId);
                DownloadNotification downloadNotification = downloadNotificationsMap.get(notificationId);
                if (downloadNotification != null) {
                    downloadNotificationsMap.remove(notificationId);
                    notify(downloadNotification.getGroupId());
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void cancelOngoingNotifications() {
        synchronized (downloadNotificationsMap) {
            try {
                Iterator<DownloadNotification> iterator = downloadNotificationsMap.values().iterator();
                DownloadNotification downloadNotification;
                while (iterator.hasNext()) {
                    downloadNotification = iterator.next();
                    if (!downloadNotification.isFailed() && !downloadNotification.isCompleted()) {
                        NotificationHelper.cancelNotification(downloadNotification.getNotificationId());
                        downloadNotificationsBuilderMap.remove(downloadNotification.getNotificationId());
                        downloadNotificationExcludeSet.remove(downloadNotification.getNotificationId());
                        iterator.remove();
                        notify(downloadNotification.getGroupId());
                    }
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    interface Function<T> {
        boolean predicate(T t);
    }

    private <T> List<T> filters(Iterable<T> source, Function<T> function) {
        List<T> newArrayList = new ArrayList<>();
        for (T t : source) {
            if (function.predicate(t)) {
                newArrayList.add(t);
            }
        }
        return newArrayList;
    }


    @Override
    public void notify(int groupId) {
        synchronized (downloadNotificationsMap) {
            List<DownloadNotification> groupedDownloadNotifications = filters(downloadNotificationsMap.values(), (it) -> it.getGroupId() == groupId);

            NotificationRequest groupSummaryNotificationRequest = getNotificationBuilder(groupId, groupId);
            boolean useGroupNotification = updateGroupSummaryNotification(groupId, groupSummaryNotificationRequest, groupedDownloadNotifications, context);
            int notificationId;
            try {
                NotificationRequest notificationRequest;
                for (DownloadNotification downloadNotification : groupedDownloadNotifications) {
                    if (shouldUpdateNotification(downloadNotification)) {
                        notificationId = downloadNotification.getNotificationId();
                        notificationRequest = getNotificationBuilder(notificationId, groupId);
                        updateNotification(notificationRequest, downloadNotification, context);
                        NotificationHelper.publishNotification(String.valueOf(notificationId), notificationRequest);
                        if (downloadNotification.getStatus() == Status.COMPLETED || downloadNotification.getStatus() == Status.FAILED) {
                            downloadNotificationExcludeSet.add(downloadNotification.getNotificationId());
                        } else {

                        }
                    }
                }
                if (useGroupNotification) {
                    NotificationHelper.publishNotification(String.valueOf(groupId), groupSummaryNotificationRequest);
                }
            } catch (ohos.rpc.RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean shouldUpdateNotification(DownloadNotification downloadNotification) {
        return !downloadNotificationExcludeSet.contains(downloadNotification.getNotificationId());
    }

    @Override
    public boolean shouldCancelNotification(DownloadNotification downloadNotification) {
        return downloadNotification.isPaused();
    }

    @Override
    public boolean postDownloadUpdate(Download download) {
        synchronized (downloadNotificationsMap) {
            if (downloadNotificationsMap.size() > 50) {
                downloadNotificationsBuilderMap.clear();
                downloadNotificationsMap.clear();
            }
            DownloadNotification downloadNotification = downloadNotificationsMap.get(download.getId());
            if (downloadNotification == null) {
                downloadNotification = new DownloadNotification();
            }
            downloadNotification.setStatus(download.getStatus());
            downloadNotification.setProgress(download.getProgress());
            downloadNotification.setNotificationId(download.getId());
            downloadNotification.setGroupId(download.getGroup());
            downloadNotification.setEtaInMilliSeconds(download.getEtaInMilliSeconds());
            downloadNotification.setDownloadedBytesPerSecond(download.getDownloadedBytesPerSecond());
            downloadNotification.setTotal(download.getTotal());
            downloadNotification.setDownloaded(download.getDownloaded());
            downloadNotification.setNamespace(download.getNamespace());
            downloadNotification.setTitle(getDownloadNotificationTitle(download));
            downloadNotificationsMap.put(download.getId(), downloadNotification);
            if (downloadNotificationExcludeSet.contains(downloadNotification.getNotificationId())
                && !downloadNotification.isFailed() && !downloadNotification.isCompleted()) {
                downloadNotificationExcludeSet.remove(downloadNotification.getNotificationId());
            }
            if (downloadNotification.isCancelledNotification() || shouldCancelNotification(downloadNotification)) {
                cancelNotification(downloadNotification.getNotificationId());
            } else {
                notify(download.getGroup());
            }
            return true;
        }
    }

    @Override
    public NotificationRequest getNotificationBuilder(int notificationId, int groupId) {
        synchronized (downloadNotificationsMap) {
            NotificationRequest notificationRequest = downloadNotificationsBuilderMap.get(notificationId);
            if (notificationRequest == null) {
                notificationRequest = new NotificationRequest();
                notificationRequest.setSlotId(getChannelId(notificationId, context));
            }
            downloadNotificationsBuilderMap.put(notificationId, notificationRequest);
            notificationRequest
                .setGroupValue(String.valueOf(notificationId))
                .setProgressBar(0, 0, false)
                .setAutoDeletedTime(Defaults.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET)
                .setGroupValue(String.valueOf(groupId))
                .setLittleIcon(createMap(ResourceTable.Media_stat_sys_download_anim0))
                .getActionButtons().clear();
            return notificationRequest;
        }
    }

    @Override
    public long getNotificationTimeOutMillis() {
        return Defaults.DEFAULT_NOTIFICATION_TIMEOUT_AFTER;
    }

    @Override
    public abstract Fetch getFetchInstanceForNamespace(String namespace);

    @Override
    public String getDownloadNotificationTitle(Download download) {
        String lastPath = download.getFileUri().getLastPath();
        if (lastPath == null) {
            if (download.getUrl().length() > 0) {
                Uri uri = Uri.parse(download.getUrl());
                if (uri != null) {
                    lastPath = uri.getLastPath();
                }
            }
        }

        if (lastPath == null) {
            lastPath = download.getUrl();
        }
        return lastPath;
    }

    @Override
    public String getSubtitleText(Context context, DownloadNotification downloadNotification) {
        if (downloadNotification.isCompleted()) {
            return "Complete";
        } else if (downloadNotification.isFailed()) {
            return "Failed";
        } else if (downloadNotification.isPaused()) {
            return "Paused";
        } else if (downloadNotification.isQueued()) {
            return "Starting";
        } else if (downloadNotification.getEtaInMilliSeconds() < 0) {
            return "Downloading";
        }
        return getEtaText(downloadNotification.getEtaInMilliSeconds());
    }

    private String getEtaText(long etaInMilliSeconds) {
        long seconds = (etaInMilliSeconds / 1000);
        long hours = (seconds / 3600);
        seconds -= (hours * 3600);
        long minutes = (seconds / 60);
        seconds -= (minutes * 60);

        String eta_hrs = "%1$dh %2$dm %3$ds left";
        String eta_min = "%1$dm %2$ds left";
        String eta_sec = "%1$ds left";

        if (hours > 0) {
            return String.format(eta_hrs, hours, minutes, seconds);
        } else if (minutes > 0) {
            return String.format(eta_min, minutes, seconds);
        } else {
            return String.format(eta_sec, seconds);
        }
    }

    @Override
    public void registerBroadcastReceiver() {
        try {
            MatchingSkills matchingSkills = new MatchingSkills();
            matchingSkills.addEvent(getNotificationManagerAction());
            CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
            broadcastReceiver = new NoticeEventSubscriber(context, this, subscribeInfo);
            CommonEventManager.subscribeCommonEvent(broadcastReceiver);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void unregisterBroadcastReceiver() {
        try {
            CommonEventManager.unsubscribeCommonEvent(broadcastReceiver);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getNotificationManagerAction() {
        return notificationManagerAction;
    }

    @Override
    public CommonEventSubscriber getBroadcastReceiver() {
        return broadcastReceiver;
    }

    private static class NoticeEventSubscriber extends CommonEventSubscriber {
        private final Context context;
        private final FetchNotificationManager manager;

        public NoticeEventSubscriber(Context context, FetchNotificationManager manager, CommonEventSubscribeInfo subscribeInfo) {
            super(subscribeInfo);
            this.context = context;
            this.manager = manager;
        }

        @Override
        public void onReceiveEvent(CommonEventData commonEventData) {
            NotificationUtils.onDownloadNotificationActionTriggered(context, commonEventData.getIntent(), manager);
        }
    }
}
